package com.zwh.common.easyexcel;

import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;

import net.sf.cglib.beans.BeanMap;

/**
 * 模板导出工具类
 *
 * @author monkey
 * 2020/11/11 16:46
 */
public class EasyExcelTemplateExportUtils {


    /**
     * 导出数据到输出流
     * @param exportList
     * @param templatePath
     * @param out
     * @throws IOException
     */
    public static void fillListToStream(Object exportList, String templatePath, OutputStream out) {
        fillListToStream(exportList, null, templatePath, out, null, null, true);
    }

    public static void fillListToStream(Object exportList, Class clazz, String templatePath, OutputStream out) {
        fillListToStream(exportList, clazz, templatePath, out, null, null, true);
    }

    public static ExcelWriter fillListToStream(Object exportList, Class clazz, String templatePath, OutputStream out, boolean isCloseExcel) {
        return fillListToStream(exportList, clazz, templatePath, out, null, null, isCloseExcel);
    }

    /**
     * Web导出模板数据
     * 自动关闭流
     *
     * @param exportList
     * @param templatePath
     * @param outFileName
     * @param response
     */
    public static void fillListToWeb(Object exportList, String templatePath, String outFileName,
                                     HttpServletResponse response) throws IOException {
        fillListToWeb(exportList, null, templatePath, outFileName, response, true);
    }

    public static ExcelWriter fillListToWeb(Object exportList, Class clazz, String templatePath, String outFileName,
                                            HttpServletResponse response, boolean isCloseExcel) throws IOException {
        return fillListToWeb(exportList, clazz, templatePath,
                outFileName, response, null, null, isCloseExcel);
    }

    /**
     * 导出数据到web Http响应
     *
     * @param exportList
     * @param clazz 导出的entity列表数据时可以指定，用于自定义注解转换数据类型
     * @param templatePath 木板路径
     * @param outFileName 输出文件名
     * @param response
     * @param writeHandler  指定sheet拦截器：暂时没用
     * @param isCloseExcel 是否自动关闭流
     */
    public static ExcelWriter fillListToWeb(Object exportList,
                                                     Class clazz,
                                                     String templatePath,
                                                     String outFileName,
                                                     HttpServletResponse response,
                                                     WriteHandler writeHandler,
                                                     FillConfig fillConfig,
                                                     boolean isCloseExcel) throws IOException {
        response.reset();
        response.setContentType("application/octet-stream; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + outFileName);
        return fillListToStream(exportList, clazz, templatePath,
                response.getOutputStream(), writeHandler, fillConfig, isCloseExcel);
    }

    /**
     * 导出数据到输出流
     *
     * @param exportList
     * @param clazz
     * @param templatePath
     * @param writeHandler
     * @param isCloseExcel 是否自动关闭流
     */
    public static ExcelWriter fillListToStream(Object exportList,
                                                     Class clazz,
                                                     String templatePath,
                                                     OutputStream out,
                                                     WriteHandler writeHandler,
                                                     FillConfig fillConfig,
                                                     boolean isCloseExcel) {
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcelTemplateExportUtils.getExcelWriter(templatePath, out);
            WriteSheet writeSheet = null;
            if (writeHandler != null) {
                writeSheet = EasyExcel.writerSheet().registerWriteHandler(writeHandler).build();
            } else {
                writeSheet = EasyExcel.writerSheet().build();
            }
            EasyExcelTemplateExportUtils.fillListToWriteSheet(excelWriter, exportList, clazz, writeSheet, fillConfig);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (isCloseExcel) {
                // 别忘记关闭流
                if (excelWriter != null) {
                    excelWriter.finish();
                }
            }
        }
        return excelWriter;
    }

    /**
     * 根据模板路径，输出文件名获取excelWriter类
     *
     * @param templatePath
     * @param outFileName
     * @param response
     */
    public static ExcelWriter getExcelWriter(String templatePath, String outFileName, HttpServletResponse response) throws IOException {
        response.reset();
        response.setContentType("application/octet-stream; charset=utf-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + outFileName);
        return getExcelWriter(templatePath, response.getOutputStream());
    }

    /**
     * 根据模板路径，输出流
     * 获取excelWriter类
     *
     * @param templatePath
     * @param out
     */
    public static ExcelWriter getExcelWriter(String templatePath, OutputStream out) throws IOException {
        ExcelWriter excelWriter = null;
        //这里指定需要表头，因为model通常包含表信头息
        excelWriter = EasyExcelFactory.write(out).withTemplate(templatePath).build();
        return excelWriter;
    }


    /**
     * 克隆模板sheet
     *
     * @param excelWriter
     * @param sourceIndex     要克隆的sheet的下标
     * @param targetIndex     克隆的sheet后，生成的sheet下标
     * @param targetSheetName 克隆后的sheet的名字
     * @param isFirst         是否是第一次克隆。
     * @return
     */
    public static WriteSheet cloneExcelSheet(ExcelWriter excelWriter, int sourceIndex, int targetIndex, String targetSheetName, boolean isFirst) {
        Workbook workBook = excelWriter.writeContext().writeWorkbookHolder().getCachedWorkbook();
        Sheet cloneSheet = workBook.cloneSheet(sourceIndex);
        workBook.setSheetName(targetIndex, targetSheetName);

        WriteSheet writeSheetItem = EasyExcel.writerSheet(targetIndex).build();
        if (isFirst) {
            excelWriter.writeContext().writeWorkbookHolder()
                    .setWorkbook(excelWriter.writeContext().writeWorkbookHolder().getCachedWorkbook());
        }
        return writeSheetItem;
    }

    /**
     * 填充数据到模板数据中。
     *
     * @param excelWriter
     * @param exportList
     * @param clazz       要导出的模板类，用于数据格式转换
     * @param writeSheet
     * @param fillConfig
     * @return
     * @throws IOException
     */
    public static ExcelWriter fillListToWriteSheet(ExcelWriter excelWriter,
                                                   Object exportList,
                                                   Class clazz,
                                                   WriteSheet writeSheet,
                                                   FillConfig fillConfig) throws Exception {
        Object realExportData = exportList;
        if (clazz != null) {
            // 根据指定类获取模板填充转换配置
            ExcelWriteFillProperty excelWriteFillProperty =
                    new ExcelWriteFillProperty(excelWriter.writeContext().writeSheetHolder(), clazz);
            Map<String, ExcelContentProperty> contentPropertyMap = excelWriteFillProperty.getFieldNameContentPropertyMap();
            if (contentPropertyMap.size() > 0) {
                List<Map<String, Object>> realExportDataList = new ArrayList<>();
                // 默认直接转换列表数据
                Object realData;
                if (exportList instanceof FillWrapper) {
                    FillWrapper fillWrapper = (FillWrapper) exportList;
                    realData = fillWrapper.getCollectionData();
                    convertCollection(realData, realExportDataList, contentPropertyMap);
                    fillWrapper.setCollectionData(realExportDataList);
                    realExportData = fillWrapper;
                } else {
                    realData = exportList;
                    convertCollection(realData, realExportDataList, contentPropertyMap);
                    realExportData = realExportDataList;
                }
            }
        }
        excelWriter.fill(realExportData, fillConfig, writeSheet);
        return excelWriter;
    }

    /**
     * 根据转换器map中对应的属性列表转换集合数据的类型
     *
     * @param realData
     * @param realExportDataList
     * @param contentPropertyMap
     * @throws Exception
     */
    public static void convertCollection(Object realData,
                                         List<Map<String, Object>> realExportDataList,
                                         Map<String, ExcelContentProperty> contentPropertyMap) throws Exception {
        if (realData instanceof Collection) {
            // 集合数据，如果是对象先转成map，后对数据类型进行转换
            Collection collectionData = (Collection) realData;
            if (CollectionUtils.isEmpty(collectionData)) {
                return;
            }
            Iterator iterator = collectionData.iterator();
            while (iterator.hasNext()) {
                Map dataMap;
                Object oneRowData = iterator.next();
                if (oneRowData instanceof Map) {
                    dataMap = (Map) oneRowData;
                } else {
                    dataMap = BeanMap.create(oneRowData);
                    // 需要转换成hashMap，才能类型转换
                    dataMap = new HashMap<String, Object>(dataMap);
                }
                for (Map.Entry<String, ExcelContentProperty> entry : contentPropertyMap.entrySet()) {
                    Object value = dataMap.get(entry.getKey());
                    ExcelContentProperty excelContentProperty = entry.getValue();
                    if (value != null) {
                        WriteCellData<?> cellData = ((Converter<Object>)excelContentProperty.getConverter())
                                .convertToExcelData(value, excelContentProperty, null);
                        dataMap.put(entry.getKey(), cellData);
                    }
                }
                realExportDataList.add(dataMap);
            }
        }
    }
}
